unit uZenerCalc;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, uBaseCalcForm, pxml, StdCtrls, ComCtrls, ExtCtrls;

type
  TfmZenerCalc = class(TfmBaseCalcForm)
    cbCategory: TComboBox;
    cbZener: TComboBox;
    PageControl1: TPageControl;
    tsZenerInfo: TTabSheet;
    tsStabCalc: TTabSheet;
    laCategory: TLabel;
    laPart: TLabel;
    edVf: TLabeledEdit;
    laVf: TLabel;
    edVz: TLabeledEdit;
    laVz: TLabel;
    edIz: TLabeledEdit;
    laIz: TLabel;
    edIsrg: TLabeledEdit;
    laIsrg: TLabel;
    edVcc: TLabeledEdit;
    laVcc: TLabel;
    edVstab: TLabeledEdit;
    laVstab: TLabel;
    edIload: TLabeledEdit;
    laILoad: TLabel;
    edIzMax: TLabeledEdit;
    laIzMax: TLabel;
    btnCalc: TButton;
    cbAutoCalc: TCheckBox;
    imgP3: TImage;
    Label1: TLabel;
    edAccu: TLabeledEdit;
    Bevel1: TBevel;
    edCalcZener: TLabeledEdit;
    Panel1: TPanel;
    imgMain: TImage;
    edCalcIzen: TLabeledEdit;
    laCalcIzen: TLabel;
    edCalcR1: TLabeledEdit;
    edCalcPzen: TLabeledEdit;
    laCalcPzen: TLabel;
    cbCalcCategory: TComboBox;
    laCalcCategory: TLabel;
    laIloadMax: TLabel;
    edIloadMax: TLabeledEdit;
    procedure cbCategoryChange(Sender: TObject);
    procedure cbZenerChange(Sender: TObject);
    procedure DoFieldChange(Sender: TObject);
    procedure imgP3Click(Sender: TObject);
    procedure DoPosFloatKeyPress(Sender: TObject; var Key: Char);
  private
    { Private declarations }
    FZenerTable: TPXMLDocument;
    procedure LoadCatList;
    procedure LoadCat(aIdx: integer);
    function FindSingleZener(AVst: single; AImax: single; Accu: single): TPXMLNode;
  protected
    function DoCheckFields: boolean; override;
    procedure FieldsChanged; override;
  public
    { Public declarations }
    class function CalcFriendlyName: string; override;
    class function RunKey: string; override;
    class procedure InvokeCalc; override;
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
  end;

  (*
  TZenerList = class;

  TZener = class
  private
    FOwner: TZenerList;
    strName: string;
    sfVz,
    sfVf,
    sfIzk,
    sfImax,
    sfIsrg: single;
  end;

  TZenerList = class
  private
    FList: TList;
  end;
  *)

var
  fmZenerCalc: TfmZenerCalc;

implementation

{$R *.dfm}

uses uFormReg, uENoms, uLocalization;

{ TfmZenerCalc }

procedure TfmZenerCalc.AfterConstruction;
begin
  inherited;
  LoadPictureFromRes(imgP3.Picture, 'WARN');

  tsZenerInfo.Caption := GetLocaleString('tabinfo');
  tsStabCalc.Caption := GetLocaleString('tabcalc');
  laCategory.Caption := GetLocaleString('category');
  laCalcCategory.Caption := GetLocaleString('category');
  laPart.Caption := GetLocaleString('part');
  edAccu.EditLabel.Caption := GetLocaleString('accuracy');
  edCalcZener.EditLabel.Caption := GetLocaleString('part');
  edCalcR1.EditLabel.Caption := 'R1';

  SetLabels('Vcc', getString('#global\vcc'), getString('#global\volt'));
  SetLabels('Vstab', getString('#global\uout'), getString('#global\volt'));
  Setlabels('Iload', getString('#global\iload'), getString('#global\ma'));
  SetLabels('CalcIzen', GetLocaleString('actizen'), getString('#global\ma'));
  SetLabels('CalcPzen', GetLocaleString('pwrdiss'), getString('#global\watt'));

  Self.SetLabels('Isrg', GetLocaleString('isrg'), getString('#global\ma'));
  Self.SetLabels('Vf', GetLocaleString('vfwd'), getString('#global\volt'));
  Self.SetLabels('Vz', GetLocaleString('vzen'), getString('#global\volt'));
  Self.SetLabels('Iz', GetLocaleString('izen'), getString('#global\ma'));
  Self.SetLabels('IzMax', GetLocaleString('izmax'), getString('#global\ma'));

  FZenerTable := TPXMLDocument.Create;
  if FileExists(AppDir+'Data\zener.xml') then
    FZenerTable.Load(AppDir+'Data\zener.xml')
  else
    FZenerTable.LoadXML('<?xml version="1.0" encoding="UTF-8" ?><data />');

  LoadCatList;
end;

procedure TfmZenerCalc.BeforeDestruction;
begin
  inherited;
  FZenerTable.Save(AppDir+'Data\zener.xml');
  FreeAndNil(FZenerTable);
end;

class function TfmZenerCalc.CalcFriendlyName: string;
begin
  Result := 'Zener Calculator';
end;

class procedure TfmZenerCalc.InvokeCalc;
begin
  Application.CreateForm(TfmZenerCalc, fmZenerCalc);
  inherited;
end;

procedure TfmZenerCalc.LoadCat(aIdx: integer);
var i: integer;
    att: TPXMLAttribute;
begin
  cbZener.Clear;
  cbZener.Tag := 0;
  att := FZenerTable.DocumentRoot[aIdx].findAttrNode('power');
  if (att<>nil) then cbZener.Tag := att.AsInteger;

  for i := 0 to FZenerTable.DocumentRoot[aIdx].ChildCount-1 do
    cbZener.AddItem(FZenerTable.DocumentRoot[aIdx][i].getAttribute('name'), FZenerTable.DocumentRoot[aIdx][i]);
end;

procedure TfmZenerCalc.LoadCatList;
var i: integer;
    w: single;
    s: string;
begin
  cbCategory.Clear;
  cbCalcCategory.Clear;
  cbCalcCategory.AddItem(GetLocaleString('allseries'),nil);
  if (FZenerTable.DocumentRoot=nil) or (FZenerTable.DocumentRoot.ChildCount=0) then exit;
  for i := 0 to FZenerTable.DocumentRoot.ChildCount-1 do begin
    w := StrToFloatDef(FZenerTable.DocumentRoot[i].getAttribute('power'), 0);
    s := Format('%s (%.1f %s)', [FZenerTable.DocumentRoot[i].getAttribute('series'), w/1000, getString('#global\watt')]);
    cbCategory.AddItem(s, FZenerTable.DocumentRoot[i]);
    cbCalcCategory.AddItem(s, FZenerTable.DocumentRoot[i]);
  end;
  cbCalcCategory.ItemIndex := 0;
end;

class function TfmZenerCalc.RunKey: string;
begin
  Result := 'zener';
end;

procedure TfmZenerCalc.cbCategoryChange(Sender: TObject);
begin
  inherited;
  LoadCat(cbCategory.ItemIndex);
end;

procedure TfmZenerCalc.cbZenerChange(Sender: TObject);
var att: TPXMLAttribute;
    data: array[0..4] of single;
begin
  inherited;
  edVf.Text := ''; edVz.Text := '';
  edIz.Text := ''; edIsrg.Text := '';
  ZeroMemory(@data, sizeOf(data));

  if (cbZener.ItemIndex<0) or (cbZener.Items.Objects[cbZener.ItemIndex]=nil) then exit;

  att := TPXMLNode(cbZener.Items.Objects[cbZener.ItemIndex]).findAttrNode('vf');
  if (att<>nil) then edVf.Text := Format('%f', [att.AsFloat]);

  att := TPXMLNode(cbZener.Items.Objects[cbZener.ItemIndex]).findAttrNode('vz');
  if (att<>nil) then data[0] := att.AsFloat;
  edVz.Text := Format('%f', [data[0]]);

  att := TPXMLNode(cbZener.Items.Objects[cbZener.ItemIndex]).findAttrNode('izk');
  if (att<>nil) then data[1] := att.AsFloat;
  edIz.Text := Format('%f', [data[1]]);

  att := TPXMLNode(cbZener.Items.Objects[cbZener.ItemIndex]).findAttrNode('imax');
  if (att<>nil) then data[2] := att.AsFloat
  else
  if (data[0]>0) then begin
    data[2] := round(cbZener.Tag/data[0]);
    if (data[2]>0) then TPXMLNode(cbZener.Items.Objects[cbZener.ItemIndex]).setAttributeL('imax', round(data[2]));
  end;
  edIzMax.Text := Format('%f', [data[2]]);

  att := TPXMLNode(cbZener.Items.Objects[cbZener.ItemIndex]).findAttrNode('isrg');
  if (att<>nil) then edIsrg.Text := Format('%f', [att.AsFloat]);
end;

procedure TfmZenerCalc.DoFieldChange(Sender: TObject);
begin
  inherited;
  Self.DoFieldChanged;
end;

function TfmZenerCalc.DoCheckFields: boolean;
begin
  Result := (checkField(edVcc)>0) and
            (checkField(edVstab)>0);
end;

function znode_attr_float(ANode: TPXMLNode; AAttrName: string): single;
var att: TPXMLAttribute;
begin
  Result := 0;
  if (ANode<>nil) then begin
    att := ANode.findAttrNode(AAttrName);
    if (att<>nil) then Result := att.AsFloat;
  end;
end;

procedure TfmZenerCalc.FieldsChanged;
var iload,izk,vcc,vstab,r1: single;
    zdata: TPXMLNode;
    vzen,accu: single;
begin
  inherited;
  edCalcZener.Text := '';
  edCalcR1.Text := '';
  edCalcIzen.Text := '';
  edCalcPzen.Text := '';

  vcc := checkField(edVcc);
  vstab := checkField(edVstab);
  accu := StrTofloatDef(edAccu.Text,0);

  if (vstab>vcc) then begin
    edCalcZener.Text := getLocaleString('vccexceed');
    Self.HighliteErrorField(edVstab);
    exit;
  end;

  Iload := StrToFloatDef(edIload.Text,0)/1e3;
  if (ILoad<0) then Iload := 0;

  // Find stab(s) by voltage
  zData := FindSingleZener(vStab, Iload, accu);
  if (zData=nil) then begin
    edCalcZener.Text := getLocaleString('errnozener');
    //Self.HighliteErrorField(edVstab);
    exit;
  end;
  
  izk := znode_attr_float(zData, 'izk')*1e-3;
  vzen := znode_attr_float(zData, 'vz');

  if (vzen<>vstab) then begin
    imgP3.Visible := true;
    imgP3.Tag := round(vzen*1e3);
  end else
    imgP3.Visible := false;

  // R1=(Uin-UVD1)/IR1
  r1 := (vcc-vstab)/(iload+izk);
  r1 := GetE24(r1);

  izk := (vcc-vstab)/r1 - iload;

  edCalcZener.Text := Format('%s (%.1f %s)', [zData.getAttribute('name'), vzen, getString('#global\volt')]);
  edCalcIzen.Text := Format('%.2f', [izk*1e3]);
  edCalcR1.Text := Format('%f %s, %f %s', [r1, getString('#global\ohm'), sqr(Iload+Izk)*r1, getString('#global\watt')]);
  edCalcPzen.Text := Format('%f...%f', [vzen*izk, vzen*(iload+izk)]);
end;

function DeltaPrc(AVal,APrev,ANext: single): integer;
begin
  if (ANext-APrev)>0 then
    Result := Round((AVal-APrev)/(ANext-APrev)*100)
  else Result := 100;
end;

function dopusk(ARef,APrc,AVal: single): boolean;
begin
  Result := abs(ARef-AVal) < (Aref*(APrc*1e-2));
end;

function TfmZenerCalc.FindSingleZener(AVst, AImax: single; Accu: single): TPXMLNode;
var c: integer;
    tmp: single;
    res: TPXMLNode;
    lst: TList;

    procedure WalkCategory(CatNode: TPXMLNode);
    var z: integer;
    begin
      for z := 0 to CatNode.ChildCount-1 do
        with CatNode do begin
          if WideSameText(Children[z].Name, 'zener') then begin
            if (AImax*1e3 > znode_attr_float(Children[z], 'imax')) then continue;
            tmp := znode_attr_float(Children[z], 'vz');
            if (tmp>AVst) and (not dopusk(AVst, Accu, tmp)) then begin
              if (res<>nil) then lst.Add(res);
              break;
            end;

            Res := Children[z];
          end;
        end;
    end;
begin
  Result := nil;
  if (FZenerTable.DocumentRoot=nil) or (FZenerTable.DocumentRoot.ChildCount=0) then exit;

  res := nil;
  lst := TList.Create;
  try

  if (cbCalcCategory.ItemIndex>-1) and (cbCalcCategory.Items.Objects[cbCalcCategory.ItemIndex]<>nil) then
    WalkCategory(TPXMLNode(cbCalcCategory.Items.Objects[cbCalcCategory.ItemIndex]))
  else
    for c := 0 to FZenerTable.DocumentRoot.ChildCount-1 do
      if WideSameText(FZenerTable.DocumentRoot[c].Name, 'category') then
        WalkCategory(FZenerTable.DocumentRoot[c]);

    if (lst.Count>0) then begin
      // we have multiple results, check imax =)
      Res := nil;
      for c := 0 to lst.Count-1 do begin
        tmp := znode_attr_float(TPXMLNode(lst[c]), 'imax');
        if (tmp >= AImax) then res := TPXMLNode(lst[c]);
      end;
    end;

    if (res<>nil) then begin
      if (not dopusk(AVst, 20, znode_attr_float(TPXMLNode(res), 'vz'))) then res := nil
      else
      if (znode_attr_float(TPXMLNode(res), 'imax')<AImax) then res := nil;
    end;
    Result := res;
  finally
    lst.Free;
  end;
end;

procedure TfmZenerCalc.imgP3Click(Sender: TObject);
begin
  inherited;
  LockFields; try
    edVstab.Text := Format('%.1f', [imgP3.Tag*1.0e-3]);
  finally UnlockFields; end;
  Self.DoFieldChanged;
end;

procedure TfmZenerCalc.DoPosFloatKeyPress(Sender: TObject; var Key: Char);
begin
  if not (Key in ['0'..'9',chr(VK_BACK),chr(VK_DELETE),DecimalSeparator]) then Key := #0;
end;

initialization
   RegisterCalc(TfmZenerCalc);
   
end.
 